package Server;

import java.io.IOException;
import java.net.Socket;
import java.net.UnknownHostException;
import java.util.Vector;

import common.BufferIO;
import common.ClientInterface;
import common.ComandParser;
import common.Ranking;
import common.RankingEntry;
import common.UserMap;
import common.exception.BusyNameException;
import common.exception.ExceptionHandler;
import common.exception.FailedAuthenticationException;
/**
 * 
 * 
 * 
 * @author jacopo
 *
 */


public class SocketClient extends Thread implements ClientInterface {

	//Canale di fomunicazione verso il client
	BufferIO bufferIO;
	private Game myGame;
	//Memorizzo il token per logout di emergenza
	int token=-1;
	//Variabile sentinella per la chiusura del thread
	boolean close=false;
	//Comandi e opzioni corrispondenti
	private String [] comandList={"","creaUtente"
			,"login",
			"creaRazza",
			"accessoPartita",
			"uscitaPartita",
			"listaGiocatori",
			"classifica",
			"logout",
			"mappaGenerale",
			"listaDinosauri",
			"vistaLocale",
			"statoDinosauro"
			,"muoviDinosauro"
			,"cresciDinosauro"
			,"deponiUovo"
			,"confermaTurno"
			,"passaTurno"};
	
	private String [] patternsClient={"\\w{2,}=(\\w+),\\w{2,}=(\\w+)",
			"\\w{2,}=(\\w+),\\w{2,}=(\\w+),\\w{2,}=(c|e)",
			",\\w+=(\\w+)",
			"\\w{2,}=(\\w+),\\w{2,}=(\\w+),\\w{2,}=\\{(\\w+),(\\w+)\\}"};
	/*Lista di regex con il loro utilizzo, messaggi client
	 * [0], creautente, gruppi 1,2
	 * [0], login,gruppi 1,2
	 * [0], statoDinosauro, gruppi 1,2
	 * [0], cresciDinosauro, gruppi 1,2
	 * [0], vistaLocale, gruppi 1,2
	 * [0], deponiUovo, gruppi 1,2
	 * [1], creaRazza, gruppi 1,2,3
	 * [2], accessoPartita, gruppi 1
	 * [2], uscitaPartita, gruppi 1
	 * [2], listaGiocatori, gruppi 1
	 * [2], classifica, gruppi 1
	 * [2], logout, gruppi 1
	 * [2], mappaGenerale, gruppi 1
	 * [2], listaDinosauri, gruppi 1
	 * [2], confermaTurno, gruppi 1
	 * [2], passaTurno, gruppi 1
	 * [3], muoviDinosauro, gruppi 1,2,3,4
	 */
	
	public SocketClient(Socket socket) throws IOException {
		super("SocketClient");
		this.myGame= Game.getGame();
		try{
		this.bufferIO=new BufferIO(socket);
		}
		//Non dovrebbe accadere
		catch(UnknownHostException e){
			//e.printStackTrace();
		}
		
		this.bufferIO.start();
		this.start();
	}

	@Override
	public void notifyTurn(String newUser) {
		String message="@"+"cambioTurno,"+newUser;
		this.bufferIO.write(message);
		System.out.println("Turno cambiato:" +newUser);


	}

	
	/**
	 * Il metodo run()is occupa di capire che metodo  e'stato chiamato via socket e chiama a sua volta il metodo
	 * giusto passando come parametro al String ricevuta come messaggio
	 * Ottine messaggio
	 * != null
	 * trova il comando giusto e chiama relativa funzione
	 * 
	 */
	public void run(){
		while(!toClose()){
			String message=null;
			if(!this.bufferIO.isEmpty())
				message=this.bufferIO.read();
			//E' morto, pe qualemotivo non lo so, ma io chiamo l'ergencyLogout
			if(!this.bufferIO.isAlive()){
				this.emegencyLogout();
				this.close();
				
			}
			
			
			if(message!=null){
				if(this.validateMessage(message)){
					String comandName=ComandParser.getName(message);

					if(comandName.equalsIgnoreCase(this.comandList[1])){
						this.newUser(message);
						continue;
					}
					else if(comandName.equalsIgnoreCase(this.comandList[2])){
						this.login(message);
						continue;
					}
					else if(comandName.equalsIgnoreCase(this.comandList[3])){
						this.newSpecies(message);
						continue;
					}
					else if(comandName.equalsIgnoreCase(this.comandList[4])){
						this.inGame(message);
						continue;
					}
					else if(comandName.equalsIgnoreCase(this.comandList[5])){
						this.outGame(message);	
						continue;
					}
					else if(comandName.equalsIgnoreCase(this.comandList[6])){
						this.getPlayers(message);
						continue;
					}
					else if(comandName.equalsIgnoreCase(this.comandList[7])){
						this.getRanking(message);
						continue;
					}
					else if(comandName.equalsIgnoreCase(this.comandList[8])){
						this.logout(message);
						continue;
					}
					else if(comandName.equalsIgnoreCase(this.comandList[9])){
						this.getMap(message);
						continue;
					}
					else if(comandName.equalsIgnoreCase(this.comandList[10])){
						this.getDinoList(message);
						continue;
					}
					else if(comandName.equalsIgnoreCase(this.comandList[11])){
						this.getDinoVisual(message);
						continue;
					}
					else if(comandName.equalsIgnoreCase(this.comandList[12])){
						this.getDinoStat(message);
						continue;
					}
					else if(comandName.equalsIgnoreCase(this.comandList[13])){
						this.move(message);
						continue;
					}
					else if(comandName.equalsIgnoreCase(this.comandList[14])){
						this.growDino(message);
						continue;
					}
					else if(comandName.equalsIgnoreCase(this.comandList[15])){
						this.egg(message);
						continue;
					}
					else if(comandName.equalsIgnoreCase(this.comandList[16])){
						this.useTurn(message);
						continue;
					}
					else if(comandName.equalsIgnoreCase(this.comandList[17])){
						this.skipTurn(message);
						continue;
					}

				}	else{
					this.comandNotValid();
					continue;
				}

			}

		}
		closeAll();


	}

	
	public void close(){
		this.close=true;
		
		
	}
	
	
	/**
	 * Ogni fuonzione ha piu' o meno questo schema:
	 * ottieni parametri in base alla regex
	 * chiama la funzione corrispondete cun i parametri giusti.
	 * se = Eccezione, transla la stringa e manda
	 * 
	 * memorizza il ritorno e costruisci la stringa da mandare al client.
	 * manda la stringa.
	 * 
	 * 
	 * @param message
	 */

	private void inGame(String message)  {
		String returnMessage="@";
		boolean wasThrown=false;
		
		
		int [] groups={1};
		String []parametres=ComandParser.parse(message, this.patternsClient[2], groups);
		try {
			this.myGame.inGame(Integer.valueOf(parametres[0]).intValue());
		} catch (Exception exception) {
			returnMessage=returnMessage.concat("no,@");
			returnMessage=returnMessage.concat(ExceptionHandler.fromExceptionToComand(exception));
			wasThrown=true;
		}
		if(!wasThrown){
			returnMessage=returnMessage.concat("ok");
			
		}
		this.bufferIO.write(returnMessage);
		
		
	}

	
	private void newSpecies(String message){
		String returnMessage="@";
		boolean wasThrown=false;
		
		
		int [] groups={1,2,3};
		String []parametres=ComandParser.parse(message, this.patternsClient[1], groups);
		try {
			this.myGame.newSpecies(Integer.valueOf(parametres[0]).intValue(),parametres[1],parametres[2].charAt(0));
		} catch (Exception exception) {
			returnMessage=returnMessage.concat("no,@");
			returnMessage=returnMessage.concat(ExceptionHandler.fromExceptionToComand(exception));
			wasThrown=true;
		}
		if(!wasThrown)
			returnMessage=returnMessage.concat("ok");
		
		this.bufferIO.write(returnMessage);
		
		
	}

	
	private void newUser(String message)  {
		String returnMessage="@";
		boolean wasThrown=false;
		
		int [] groups={1,2};
		String []parametres=ComandParser.parse(message, this.patternsClient[0], groups);
		try {
			this.myGame.newUser(parametres[0], parametres[1]);
		} catch (BusyNameException exception) {
			returnMessage=returnMessage.concat("no,@");
			returnMessage=returnMessage.concat(ExceptionHandler.fromExceptionToComand(exception));
			wasThrown=true;
		}
		if(!wasThrown)
			returnMessage=returnMessage.concat("ok");
		
		this.bufferIO.write(returnMessage);
		
		
	}

	
	private void growDino(String message) {
		String returnMessage="@";
		boolean wasThrown=false;
		
		
		
		int [] groups={1,2};
		String []parametres=ComandParser.parse(message, this.patternsClient[0], groups);
		try {
			this.myGame.growDino(Integer.valueOf(parametres[0]).intValue(), Integer.valueOf(parametres[1]).intValue());
		} catch (Exception exception) {
			returnMessage=returnMessage.concat("no,@");
			returnMessage=returnMessage.concat(ExceptionHandler.fromExceptionToComand(exception));
			wasThrown=true;
		}
		if(!wasThrown){
			returnMessage=returnMessage.concat("ok");
		
		}
		this.bufferIO.write(returnMessage);
		
	}

	
	private void egg(String message) {
		String returnMessage="@";
		boolean wasThrown=false;
		int IDdino=0;
		
		
		int [] groups={1,2};
		String []parametres=ComandParser.parse(message, this.patternsClient[0], groups);
		try {
			IDdino=this.myGame.egg(Integer.valueOf(parametres[0]).intValue(), Integer.valueOf(parametres[1]).intValue());
		} catch (Exception exception) {
			returnMessage=returnMessage.concat("no,@");
			returnMessage=returnMessage.concat(ExceptionHandler.fromExceptionToComand(exception));
			wasThrown=true;
		}
		if(!wasThrown){
			returnMessage=returnMessage.concat("ok,"+IDdino);
		
		}
		this.bufferIO.write(returnMessage);
		
	}


	private void getRanking(String message)  {
		
		String returnMessage="@";
		boolean wasThrown=false;
		
		RankingEntry []playerList=null;
		Ranking myRanking=null;
		int [] groups={1};
		String []parametres=ComandParser.parse(message, this.patternsClient[2], groups);
		try {
			
			myRanking=this.myGame.getRanking(Integer.valueOf(parametres[0]).intValue());
		} catch (Exception exception) {
			returnMessage=returnMessage.concat("no,@");
			returnMessage=returnMessage.concat(ExceptionHandler.fromExceptionToComand(exception));
			wasThrown=true;
		}
		if(!wasThrown){
			returnMessage=returnMessage.concat("classifica");
			playerList=myRanking.getRank(myRanking.getPlayers().size());
			for(RankingEntry entry: playerList){
				
				String state="s";
				if(entry.getState()==RankingEntry.State.EXTINCT)
					state="n";
				
				returnMessage=returnMessage.concat(",{"+entry.getUsername()+","+entry.getSpeciesName()+","+entry.getScore()+","+state+"}");
				
			}
			
			
		}
			
		
		this.bufferIO.write(returnMessage);
	}


	private void getPlayers(String message)  {
		String returnMessage="@";
		boolean wasThrown=false;
	
		
		String [] playerList=null;
		
		int [] groups={1};
		String []parametres=ComandParser.parse(message, this.patternsClient[2], groups);
		try {
			playerList=this.myGame.getPlayers(Integer.valueOf(parametres[0]).intValue());
		} catch (Exception exception) {
			returnMessage=returnMessage.concat("no,@");
			returnMessage=returnMessage.concat(ExceptionHandler.fromExceptionToComand(exception));
			wasThrown=true;
		}
		if(!wasThrown){
			returnMessage=returnMessage.concat(this.comandList[6]);
			for(String player: playerList)
				returnMessage=returnMessage.concat(","+player);
			
		}
			
		
		this.bufferIO.write(returnMessage);
		
		
	}


	private void getDinoList(String message)  {
		String returnMessage="@";
		boolean wasThrown=false;
		Integer[]dinoList=null;
		
		
		int [] groups={1};
		String []parametres=ComandParser.parse(message, this.patternsClient[2], groups);
		try {
			dinoList=this.myGame.getDinoList(Integer.valueOf(parametres[0]).intValue());
		} catch (Exception exception) {
			returnMessage=returnMessage.concat("no,@");
			returnMessage=returnMessage.concat(ExceptionHandler.fromExceptionToComand(exception));
			wasThrown=true;
		}
		if(!wasThrown){
			returnMessage=returnMessage.concat(this.comandList[10]);
			if(dinoList.length>0)
				for(Integer idDino: dinoList)
					returnMessage=returnMessage.concat(","+idDino);					
		
		}
		this.bufferIO.write(returnMessage);
		
	}

	
	private void getMap(String message)  {
	
		String returnMessage="@";
		boolean wasThrown=false;
		char [][]map=null;
		UserMap userMap=null;

		int [] groups={1};
		String []parametres=ComandParser.parse(message, this.patternsClient[2], groups);
		try {
			
			userMap=this.myGame.getMap(Integer.valueOf(parametres[0]).intValue());
		} catch (Exception exception) {
			returnMessage=returnMessage.concat("no,@");
			returnMessage=returnMessage.concat(ExceptionHandler.fromExceptionToComand(exception));
			wasThrown=true;
		}
		if(!wasThrown){
			map=new char[userMap.getSize()][userMap.getSize()];
			int i;
			int j;
			
			for(i=0;i<userMap.getSize(); i++){
				for(j=0; j<userMap.getSize();j++){
					//ottengo array con coordinate 
			
					int []coordinates={i,j};
					switch (userMap.typeOf(coordinates)) {
					case WATER:
						map[i][j]='a';
							break;
					case LAND:
						map[i][j]='t';
							break;
					case VEGE:
						map[i][j]='v';
							break;
					case DARK :
						map[i][j]='b';
						break;					
					case DINO:
						map[i][j]='d';
						break;
					case MEAT:
						map[i][j]='c';
						break;
					default:
						//System.err.println(userMap.typeOf(coordinates));
						break;
					}
					
				}
				
			}
			returnMessage=returnMessage.concat(this.comandList[9]+",{"+userMap.getSize()+","+userMap.getSize()+"},");
			for(i=0;i< map.length;i++){
				for(j=0;j< map.length;j++){
					returnMessage=returnMessage.concat("["+map[i][j]+"]");
				}
				returnMessage=returnMessage.concat(";");
			}
			
		
		}
		this.bufferIO.write(returnMessage);
	

	}
		


	private void getDinoStat(String message){
		String returnMessage="@";
		boolean wasThrown=false;
		Vector <Object>dinoStat=null;
		int myDinoParamNumber=8;
		
		
		int [] groups={1,2};
		String []parametres=ComandParser.parse(message, this.patternsClient[0], groups);
		try {
			dinoStat=this.myGame.getDinoStat(Integer.valueOf(parametres[0]).intValue(), Integer.valueOf(parametres[1]).intValue());
			
		} catch (Exception exception) {
			returnMessage=returnMessage.concat("no,@");
			returnMessage=returnMessage.concat(ExceptionHandler.fromExceptionToComand(exception));
			wasThrown=true;
		}
		if(!wasThrown){
			returnMessage=returnMessage.concat(this.comandList[12]+","+dinoStat.elementAt(0)+","+dinoStat.elementAt(1)+","+dinoStat.elementAt(2)+",{"+dinoStat.elementAt(3)+","+dinoStat.elementAt(4)+"},"+dinoStat.elementAt(5));
			if(dinoStat.size()==myDinoParamNumber)
				returnMessage=returnMessage.concat(","+dinoStat.elementAt(6)+","+dinoStat.elementAt(7));
		
		}
		this.bufferIO.write(returnMessage);
		
		
		
		
	}


	private void getDinoVisual(String message){

		String returnMessage="@";
		boolean wasThrown=false;
		char [][]map=null;
		UserMap userMap=null;
		
		int [] groups={1,2};
		String []parametres=ComandParser.parse(message, this.patternsClient[0], groups);
		try {
			
			userMap=this.myGame.getDinoVisual(Integer.valueOf(parametres[0]).intValue(), Integer.valueOf(parametres[1]).intValue());
		} catch (Exception exception) {
			returnMessage=returnMessage.concat("no,@");
			returnMessage=returnMessage.concat(ExceptionHandler.fromExceptionToComand(exception));
			wasThrown=true;
		}
	
		if(!wasThrown){
			map=new char[userMap.getSize()][userMap.getSize()];
			for(int i=0;i<userMap.getSize(); i++){
				for(int j=0; j<userMap.getSize();j++){
					//ottengo array con coordinate 
					int []coordinates={i+userMap.getOriginX(),j+userMap.getOriginY()};
					try{
						switch (userMap.typeOf(coordinates)) {
						case WATER:
							map[i][j]='a';
								break;
						case LAND:
							map[i][j]='t';
								break;
						case VEGE:
							map[i][j]='v';
								break;
						case DARK :
							map[i][j]='b';
							break;
						case DINO:
							map[i][j]='d';
							break;		
						case MEAT:
							map[i][j]='c';
							break;
						default:
							break;
						}
					}catch(NullPointerException e){map[i][j]='n';}
				}
			}
			/*Concatena {x,y},{sixe,size}
			 * aggiungi nuovi valori, se ==c|v|d
			 * 	ottieni valore numerici per questi campi e li accoda
			 * 
			 */
			returnMessage=returnMessage.concat(this.comandList[9]+",{"+userMap.getOriginX()+","+userMap.getOriginY()+"}"+",{"+userMap.getSize()+","+userMap.getSize()+"},");
			for(int i=0;i< map.length;i++){
				for(int j=0;j< map.length;j++){
					returnMessage=returnMessage.concat("["+map[i][j]);
					switch (map[i][j]) {
					case 'v':case 'c':case 'd':
						int []coordinates={i+userMap.getOriginX(),j+userMap.getOriginY()};
						returnMessage=returnMessage.concat(","+userMap.valueOf(coordinates));
						break;
					}
					returnMessage=returnMessage.concat("]");
				}
				returnMessage=returnMessage.concat(";");
			}
		}
		this.bufferIO.write(returnMessage);
		
	}


	private void move(String message){
		String returnMessage="@";
		boolean wasThrown=false;
		char result=0;
		
		
		int [] groups={1,2,3,4};
		String []parametres=ComandParser.parse(message, this.patternsClient[3], groups);
		try {
			int []destination={Integer.valueOf(parametres[2]).intValue(),Integer.valueOf(parametres[3]).intValue()};
			result=this.myGame.move(Integer.valueOf(parametres[0]).intValue(), Integer.valueOf(parametres[1]).intValue(), destination);
		} catch(RuntimeException exception){
			returnMessage=returnMessage.concat("no,@");
			returnMessage=returnMessage.concat(ExceptionHandler.fromExceptionToComand(exception));
			wasThrown=true;
		}
		catch (Exception e) {
			returnMessage=returnMessage.concat("no,@");
			returnMessage=returnMessage.concat(ExceptionHandler.fromExceptionToComand(e));
			wasThrown=true;
		}
		if(!wasThrown){
			if(result=='p'||result=='v')
				returnMessage=returnMessage.concat("combattimento,"+result);
			else
				returnMessage=returnMessage.concat("ok");
		
		}
		this.bufferIO.write(returnMessage);
		

	}


	private void login(String message){
		String returnMessage="@";
		boolean wasThrown=false;
		int token=-1;
		
		int [] groups={1,2};
		String []parametres=ComandParser.parse(message, this.patternsClient[0], groups);
		try {
			token=this.myGame.login(parametres[0],parametres[1]);
		} catch (FailedAuthenticationException exception) {
			returnMessage=returnMessage.concat("no,@");
			returnMessage=returnMessage.concat(ExceptionHandler.fromExceptionToComand(exception));
			wasThrown=true;
		}
		if(!wasThrown){
			returnMessage=returnMessage.concat("ok,"+token);
			this.token=token;
		}
		
		
		this.bufferIO.write(returnMessage);
	
	}

	
	private void logout(String message)  {
		String returnMessage="@";
		boolean wasThrown=false;
		
				
		int [] groups={1};
		String []parametres=ComandParser.parse(message, this.patternsClient[2], groups);
		try {
			this.myGame.logout(Integer.valueOf(parametres[0]).intValue());
		} catch (Exception exception) {
			returnMessage=returnMessage.concat("no,@");
			returnMessage=returnMessage.concat(ExceptionHandler.fromExceptionToComand(exception));
			wasThrown=true;
		}
		if(!wasThrown){
			returnMessage=returnMessage.concat("ok");
		}
			
		
		this.bufferIO.write(returnMessage);
		this.token=-1;
	
		
	}

	
	private void skipTurn(String message)  {
		String returnMessage="@";
		boolean wasThrown=false;

		int [] groups={1};
		String []parametres=ComandParser.parse(message, this.patternsClient[2], groups);
		try {
			this.myGame.skipTurn(Integer.valueOf(parametres[0]).intValue());
		} catch (Exception exception) {
			returnMessage=returnMessage.concat("no,@");
			returnMessage=returnMessage.concat(ExceptionHandler.fromExceptionToComand(exception));
			wasThrown=true;
		}
		if(!wasThrown){
			returnMessage=returnMessage.concat("ok");

		}
		this.bufferIO.write(returnMessage);
		
	}


	private void useTurn(String message) {
		String returnMessage="@";
		boolean wasThrown=false;
		
		
		
		int [] groups={1};
		String []parametres=ComandParser.parse(message, this.patternsClient[2], groups);
		try {
			this.myGame.useTurn(Integer.valueOf(parametres[0]).intValue());
		} catch (Exception exception) {
			returnMessage=returnMessage.concat("no,@");
			returnMessage=returnMessage.concat(ExceptionHandler.fromExceptionToComand(exception));
			wasThrown=true;
		}
		if(!wasThrown){
			returnMessage=returnMessage.concat("ok");
		
		}
		this.bufferIO.write(returnMessage);
		
	}

	
	private void outGame(String message)  {
		String returnMessage="@";
		boolean wasThrown=false;
		
		
		int [] groups={1};
		String []parametres=ComandParser.parse(message, this.patternsClient[2], groups);
		try {
			this.myGame.outGame(Integer.valueOf(parametres[0]).intValue());
		} catch (Exception exception) {
			returnMessage=returnMessage.concat("no,@");
			returnMessage=returnMessage.concat(ExceptionHandler.fromExceptionToComand(exception));
			wasThrown=true;
		}
		if(!wasThrown)
			returnMessage=returnMessage.concat("ok");
		
		this.bufferIO.write(returnMessage);
		
		
	}
	
	private void comandNotValid(){
		String returnMessage="@comandoNonValido";
		this.bufferIO.write(returnMessage);
	}
	
	private boolean validateMessage(String message){
		boolean isValid=false;
		String []validRegex={"@creaUtente,user=[\\w|\\d]+,pass=[\\w|\\d]+"
				,"@login,user=[\\w|\\d]+,pass=[\\w|\\d]+"
				,"@creaRazza,token=[\\w|\\d]+,nome=[\\w|\\d]+,tipo=[c|e]"
				,"@accessoPartita,token=[\\w\\d]+"
				,"@uscitaPartita,token=[\\w\\d]+"
				,"@listaGiocatori,token=[\\w\\d]+"
				,"@classifica,token=[\\w\\d]+"
				,"@logout,token=[\\w\\d]+"
				,"@mappaGenerale,[\\w\\d]+"
				,"@listaDinosauri,[\\w\\d]+"
				,"@vistaLocale,token=[\\w\\d]+,idDino=[\\w\\d]+"
				,"@statoDinosauro,token=[\\w\\d]+,idDino=[\\w\\d]+"
				,"@muoviDinosauro,token=[\\w\\d]+,idDino=[\\w\\d]+,dest=\\{[\\d]+,[\\d]+\\}"
				,"@cresciDinosauro,token=[\\w\\d]+,idDino=[\\w\\d]+"
				,"@deponiUovo,token=[\\w\\d]+,idDino=[\\w\\d]+"
				,"@confermaTurno,token=[\\w|\\d]+"
				,"@passaTurno,token=[\\w|\\d]+"};
		java.util.regex.Matcher matcher;
		for(String regex: validRegex){
			matcher=java.util.regex.Pattern.compile(regex).matcher(message);
			if(matcher.find()&&matcher.groupCount()==0){
				isValid=true;
				break;
			}
		}
		
		return isValid;
		
	}
	
	private void emegencyLogout(){
		if(this.token !=-1)

			try {
				this.myGame.logout(this.token);
			} catch (Exception e) {	}
			this.close();
	}
	
	
	private boolean toClose(){
		return this.close;
	}
	
	private void closeAll(){
		
		this.bufferIO.close();		
		
	}

}
